//
// Created by 10741 on 2021/4/29.
//
#include <iostream>
#include <fstream>
#include "catalogTree.h"

using namespace std;

catalogTree::catalogTree() : root(true, &root, "root"){
    cur = &root;
}

catalogTree::~catalogTree() = default;

void catalogTree::shell(){
    string line;
    while(true){
        getline(cin, line);
        stringstream theLine(line);

        string inst;
        theLine >> inst;
        if(inst == "dir"){
            dir(theLine);
        }else if(inst == "cd"){
            cd(theLine);
        }else if(inst == "mkdir"){
            mkdir(theLine);
        }else if(inst == "mkfile"){
            mkfile(theLine);
        }else if(inst == "delete"){
            remove(theLine);
        }else if(inst == "save"){
            save(theLine);
        }else if(inst == "load"){
            load(theLine);
        }else if(inst == "tree"){
            tree(theLine);
        }else if(inst == "move"){
            _move(theLine);
        }else if(inst == "quit"){
            return;
        }else{
            cout << "invalid instruction " << inst << ".\n";
        }
    }
}

void catalogTree::dir(stringstream &in){
    cur->dir();
}

file *catalogTree::findPath(string &path){
    stringstream thePath(path);
    file *destDir;
    if(path[0] == '/'){
        destDir = &root;
        thePath.get();
    }else{
        destDir = cur;
    }

    string nextDir;
    while(getline(thePath, nextDir, '/')){
        if(nextDir == "."){ continue; }
        else if(nextDir == ".."){ destDir = destDir->parent; }
        else{
            int type = destDir->findName(nextDir);
            if(type == 1){
                destDir = destDir->subDir[nextDir];
            }else{
                return nullptr;
            }
        }
    }
    return destDir;
}

void catalogTree::cd(stringstream &in){
    string path;
    in >> path;
    if(path.empty()){
        cout << cur->absolutePath() << endl;
    }else{
        bool detailed = false;
        string parameter;
        while(in >> parameter){
            if(parameter == "-d"){
                detailed = true;
            }else{
                cout << "Error: unknown parameter \"" << parameter << "\".\n";
                return;
            }
        }

        file *destDir = findPath(path);
        if(destDir == nullptr){
            if(detailed)cout << "Error: invalid path.\n";
        }else{
            cur = destDir;
            if(detailed)cout << "Success.\n";
        }
    }
}

void catalogTree::mkdir(stringstream &in){
    string fileName;
    in >> fileName;
    if(! validName(fileName)){
        cout << "Error: the directory name syntax is incorrect." << endl;
        return;
    }
    int type = cur->findName(fileName);
    if(type == 2){
        cout << "Error: a file " << fileName << " already exists." << endl;
    }else if(type == 1){
        cout << "Error: a subdirectory " << fileName << " already exists." << endl;
    }else{
        cur->subDir[fileName] = new file(true, cur, fileName);
    }
}

void catalogTree::mkfile(stringstream &in){
    string fileName;
    in >> fileName;
    if(! validName(fileName)){
        cout << "Error: the filename syntax is incorrect." << endl;
        return;
    }
    int type = cur->findName(fileName);
    if(type == 2){
        cout << "Error: a file " << fileName << " already exists." << endl;
    }else if(type == 1){
        cout << "Error: a subdirectory " << fileName << " already exists." << endl;
    }else{
        cur->subFile[fileName] = new file(false, cur, fileName);
    }
}

void catalogTree::remove(stringstream &in){
    string filePath;
    in >> filePath;
    string fileName = split(filePath);

    file *destDir = findPath(filePath);
    if(destDir == nullptr){
        cout << "Error: path not exists.\n";
        return;
    }

    int type = destDir->findName(fileName);
    if(type == 2){
        auto it = destDir->subFile.find(fileName);
        delete it->second;
        destDir->subFile.erase(it);
    }else if(type == 1){
        auto it = destDir->subDir.find(fileName);
        delete it->second;
        destDir->subDir.erase(it);
    }
}

void catalogTree::save(stringstream &in){
    string savePath;
    in >> savePath;
    ofstream writer(savePath);
    if(! writer.is_open()){
        cout << "Failed to save the catalog tree." << endl;
        return;
    }

    bool isCompressed = false;
    string parameter;
    while(in >> parameter){
        if(parameter == "-c"){
            isCompressed = true;
        }else{
            cout << "Error: unknown parameter \"" << parameter << "\".\n";
            return;
        }
    }

    writer << "<catalogTree>";
    if(! isCompressed)writer << "\n";
    root.save(writer, 0, isCompressed);
    writer << "</catalogTree>";
    writer.close();
}

void catalogTree::load(stringstream &in){
    string loadPath;
    in >> loadPath;
    ifstream reader(loadPath);
    if(! reader.is_open()){
        cout << "Failed to load the catalog tree." << endl;
        return;
    }

    root.clear();
    string content, tag;
    getline(reader, content, '<');
    getline(reader, tag, '>');
    root.load(reader);
}

bool catalogTree::validName(const string &s){
    if(s.empty() or s == "." or s == "..")return false;
    for(auto i: s){
        if(i == '/' or i == '<' or i == '>')return false;
    }
    return true;
}

void catalogTree::tree(stringstream &in){
    string filePath;
    in >> filePath;
    string left;
    if(filePath.empty()){
        cur->tree(left);
    }else{
        file *destDir = findPath(filePath);
        if(destDir == nullptr){
            cout << "Error: invalid path.\n";
        }else{
            destDir->tree(left);
        }
    }
}

void catalogTree::_move(stringstream &in){
    string sourcePath, destPath;
    in >> sourcePath >> destPath;
    string sourceFile = split(sourcePath);
    string destFile = split(destPath);

    bool copy = false;
    string parameter;
    while(in >> parameter){
        if(parameter == "-c"){
            copy = true;
        }else{
            cout << "Error: unknown parameter \"" << parameter << "\".\n";
            return;
        }
    }

    file *sourceDir = findPath(sourcePath);
    file *destDir = findPath(destPath);

    if(sourceDir == nullptr){
        cout << "Error: invalid source path.\n";
        return;
    }
    if(destDir == nullptr){
        cout << "Error: invalid destination path.\n";
        return;
    }

    int type = sourceDir->findName(sourceFile);
    if(type == 0){
        cout << "Error: source file/directory not exists.\n";
        return;
    }

    if(! validName(destFile)){
        cout << "Error: the file/directory name syntax is incorrect.\n";
        return;
    }

    if(destDir->findName(destFile)){
        cout << "Error: a file/directory already exists.\n";
        return;
    }

    if(type == 1){
        if(copy){
            destDir->subDir[destFile] = new file(*(sourceDir->subDir[sourceFile]), destDir);
        }else{
            file *theSourceFile = sourceDir->subDir[sourceFile];

            for(file *i = destDir; i != i->parent; i = i->parent){
                if(i == theSourceFile){
                    cout << "Error: destination directory can not be a sub directory of source file.\n";
                    return;
                }
            }
            theSourceFile->parent = destDir;
            theSourceFile->fileName = destFile;
            destDir->subDir[destFile] = theSourceFile;
            sourceDir->subDir.erase(sourceFile);
        }
    }else{
        if(copy){
            destDir->subFile[destFile] = new file(*(sourceDir->subFile[sourceFile]), destDir);
        }else{
            file *theSourceFile = sourceDir->subFile[sourceFile];
            theSourceFile->parent = destDir;
            theSourceFile->fileName = destFile;
            destDir->subFile[destFile] = theSourceFile;
            sourceDir->subFile.erase(sourceFile);
        }
    }
}

string catalogTree::split(string &path){
    int last = path.size();
    while(last and path[last - 1] != '/')last --;
    string res(path.substr(last));
    if(last){ path.resize(last - 1); }
    else{ path.clear(); }
    return res;
}
